/**
 * 
 */
package net.toocruel.iqismart.service.impl;

import net.coobird.thumbnailator.Thumbnails;
import net.toocruel.iqismart.dao.*;
import net.toocruel.iqismart.dao.condition.UserCondition;
import net.toocruel.iqismart.dao.core.AnswerRepository;
import net.toocruel.iqismart.dao.core.CreditRepository;
import net.toocruel.iqismart.dao.spec.UserSpec;
import net.toocruel.iqismart.entity.constants.Action;
import net.toocruel.iqismart.entity.constants.NotificationType;
import net.toocruel.iqismart.entity.constants.UserStatus;
import net.toocruel.iqismart.entity.core.Credit;
import net.toocruel.iqismart.entity.rbac.Role;
import net.toocruel.iqismart.entity.rbac.User;
import net.toocruel.iqismart.entity.setting.Setting;
import net.toocruel.iqismart.security.core.exception.MyUsernameNotFoundException;
import net.toocruel.iqismart.service.UserService;
import net.toocruel.iqismart.service.core.EmailService;
import net.toocruel.iqismart.service.dto.ProfileBaseDTO;
import net.toocruel.iqismart.service.dto.ProfileEmailDTO;
import net.toocruel.iqismart.service.dto.ProfilePasswordDTO;
import net.toocruel.iqismart.service.exception.BusinessException;
import net.toocruel.iqismart.service.exception.UserNameRepeatException;
import net.toocruel.iqismart.utils.FileUploadUtils;
import net.toocruel.iqismart.utils.MyBeanUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindException;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author toocruel
 *
 */
@Service
@Transactional
public class UserServiceImpl implements UserService,UserDetailsService {

	private Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private UserDetailRepository userDetailRepository;

	@Autowired
	private UserDataRepository userDataRepository;

	@Autowired
	private UserOauthRepository userOauthRepository;
	
	@Autowired
	private RoleRepository roleRepository;
	

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Autowired
	private CreditRepository creditRepository;

	@Autowired
	private AnswerRepository answerRepository;

	@Autowired
	private EmailService emailService;

	@Override
	@Transactional
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		logger.info("表单登录用户名:" + username);
		User user = userRepository.findByUsername(username);

		/**
		 *  找不到用户应该抛出UsernameNotFoundException而不是返回null(这是spring security的规则）
		 *  但是 spring security内部捕获了该异常 需要抛出自己的异常
		 */
		if(user == null){
			throw new MyUsernameNotFoundException("用户不存在");
		}

		user = this.getById(user.getId());
//		user.getUrls();


		return user;
	}


	@Override
	public User create(User user) {

		User byUsername = userRepository.findByUsername(user.getUsername());
		if(byUsername!=null){
			 throw new UserNameRepeatException("用户名重复：已存在一个用户名为"+user.getUsername()+"的用户");
		}

		user.setPassword(passwordEncoder.encode(user.getPassword()));

		Collection<Role> roleList = roleRepository.findByIdIn(user.getRoleIds());
		user.setRoles(roleList.stream().collect(Collectors.toSet()));
		userRepository.save(user);

		return user;
	}

	@Override
	public User update(User user) {
		User _user = this.getById(user.getId());

		//注意这里 因为User OneToOne关联了UserDetail 和 UserData ，为保证这两个属性是托管状态 需要 单独赋值
		MyBeanUtils.copyProperties(user,_user,"data","detail","password");
		if(StringUtils.isNotBlank(user.getPassword())){
		    _user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
		MyBeanUtils.copyProperties(user.getDetail(),_user.getDetail());
		MyBeanUtils.copyProperties(user.getData(),_user.getData());

		Collection<Role> roleList = roleRepository.findByIdIn(user.getRoleIds());
		_user.setRoles(roleList.stream().collect(Collectors.toSet()));
		userRepository.save(_user);
		return _user;
	}


	@Override
	public void delete(Long id) {
		userRepository.delete(id);
	}

	@Override
	public int deleteMany(Long[] ids) {
		return userRepository.deleteByIdIn(ids);
	}


	@Override
	public Page<User> query(UserCondition condition, Pageable pageable) {
		Page<User> users = userRepository.findAll(new UserSpec(condition), pageable);
		return users;
	}

	@Override
	public int verify(Long id) {
		User user = userRepository.findById(id);
		user.setStatus(UserStatus.REVIEW_PASS);
		userRepository.save(user);
		return 1;
	}

	@Override
	public int verifyMany(Long[] ids) {
		Arrays.stream(ids).forEach(id->{
			this.verify(id);
		});
		return 1;
	}

	@Override
	public User findById(Long id) {
		return userRepository.findById(id);
	}

	@Override
	public User getById(Long id) {
		User one = userRepository.findOne(new Specification<User>() {
			@Override
			public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				root.fetch("data");
				root.fetch("detail");
				root.fetch("roles");
				return cb.equal(root.get("id"), id);
			}
		});
		return one;
	}

	@Override
	public List<User> findAll() {
		return userRepository.findAll();
	}

	@Override
	public List<User> getHotUsers() {
//		return userRepository.getHotUsers();

		return userRepository.findAll(new PageRequest(0,10,new Sort(Sort.Direction.DESC,"data.credit"))).getContent();
	}

	@Override
	@Transactional(isolation = Isolation.REPEATABLE_READ)
	public void addCredit(Credit credit) {
        Date today = null;
        try {
            SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
            today = sf.parse(sf.format(new Date()));
        } catch (ParseException e) {
            e.printStackTrace();
        }
	    switch (credit.getAction()){
            case SIGN:
                Date finalToday = today;
                long count = creditRepository.count((root, query, cb) -> {
                    return cb.and(cb.equal(root.get("user"), credit.getUser()), cb.greaterThan(root.get("createdAt"), finalToday.getTime()));
                });
                if(count>0){
                    return;
                }
                break;
            case REGISTER:break;
            case CREATE_QUESTION:break;
            case CREATE_ANSWER:break;
            case CREATE_ARTICLE:break;
        }
		if(credit.getCoin()!=0){
			addCoin(credit.getUser().getId(),credit.getCoin());
		}

		if(credit.getCredit()!=0){
			addCredit(credit.getUser().getId(),credit.getCredit());
		}
		creditRepository.save(credit);
	}

	@Override
	@Transactional(isolation = Isolation.REPEATABLE_READ)
	public void addCoin(Long userId, int count) {
		User user = userRepository.findOne(userId);
		user.getData().setCoin(user.getData().getCoin()+count);
		userRepository.save(user);
	}

	@Override
	@Transactional(isolation = Isolation.REPEATABLE_READ)
	public void addCredit(Long userId, int count) {
		User user = userRepository.findOne(userId);
		user.getData().setCredit(user.getData().getCredit()+count);
		userRepository.save(user);
	}

	@Override
	public void updateAdoptPercent(Long userId){
		//更新answer.user 的采纳率
		try {
			User user = userRepository.findOne(userId);
			long totalAnswers = answerRepository.count((root, query, cb) -> cb.equal(root.get("user"), user));
			long adopted = answerRepository.count((root, query, cb) -> cb.and(cb.equal(root.get("user"), user),cb.isNotNull(root.get("adoptedAt"))));
			if(totalAnswers == 0 || adopted == 0){
				user.getData().setAdoptPercent(0);
				userRepository.save(user);
				return;
			}
			NumberFormat numberFormat = NumberFormat.getInstance();
			numberFormat.setMaximumFractionDigits(2);
			String percent = numberFormat.format((float) adopted / (float) totalAnswers*100);
			user.getData().setAdoptPercent(Double.parseDouble(percent));
			userRepository.save(user);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}
	}

	@Override
	public List<User> getMostCoinUsers(int count) {
		return userRepository.findAll(new PageRequest(0,count,new Sort(Sort.Direction.DESC,"data.coin"))).getContent();
	}

	@Override
	public User register(User userForm) {
		User user = new User();
		BeanUtils.copyProperties(userForm,user);
		if(!user.getPassword().equals(user.getPasswordConfirmation())){
			throw new BusinessException("passwordConfirmation","两次输入的密码不一致");
		}

		if(StringUtils.isNotBlank(user.getUsername()) && userRepository.findByUsername(user.getUsername())!=null){
			throw new BusinessException("username","用户名重复");
		}
		if(StringUtils.isNotBlank(user.getEmail()) && userRepository.findByEmail(user.getEmail())!=null){
			throw new BusinessException("email","邮箱重复");
		}

		if(StringUtils.isNotBlank(user.getMobile()) && userRepository.findByMobile(user.getMobile())!=null){
			throw new BusinessException("mobile","手机号重复");
		}



		user.setPassword(passwordEncoder.encode(user.getPassword()));
		Collection<Role> roles = roleRepository.findByIdIn(new Long[]{1L});
		user.setRoles(roles.stream().collect(Collectors.toSet()));

		user = userRepository.save(user);
		user.setAvatar("/image/avatar/"+user.getId());
		userRepository.save(user);

		//积分奖励
		Credit credit = new Credit();
		credit.setAction(Action.REGISTER);
		if (Setting.SETTING.getCoin().getRegister()>0) {
			credit.setCoin(Setting.SETTING.getCoin().getRegister());
		}
		credit.setUser(user);
		if (Setting.SETTING.getCredit().getRegister()>0) {
			credit.setCredit(Setting.SETTING.getCredit().getRegister());
		}
		this.addCredit(credit);

		return user;
	}

	@Override
	public User getByUsername(String username) {
		return this.getById(userRepository.findByUsername(username).getId());
	}

	@Autowired
	private FileUploadUtils fileUploadUtils;
	@Override
	public void updateProfileBase(Long id, ProfileBaseDTO dto) {
		User user = userRepository.findOne(id);
		if (!dto.getQrcodeFile().isEmpty()) {
			try {
				String path = fileUploadUtils.upload(dto.getQrcodeFile().getInputStream(), dto.getQrcodeFile().getOriginalFilename(),200,300);
				user.getDetail().setQrcode(path);
			} catch (IOException e) {
				e.printStackTrace();
				throw new BusinessException("文件上传失败");
			}


		}
		user.setNickname(dto.getNickname());
		user.setAvatar(dto.getAvatar());
		user.getDetail().setBirthday(dto.getBirthday());
		user.getDetail().setCity(dto.getCity());
		user.getDetail().setProvince(dto.getProvince());
		user.getDetail().setTitle(dto.getTitle());
		user.getDetail().setDescription(dto.getDescription());
		user.getDetail().setGender(dto.getGender());


		userRepository.save(user);
	}

	@Override
	public String corpAvatar(Long id,int x,int y,int width,int height) {
		try {
			User user = userRepository.findOne(id);
			String avatar = fileUploadUtils.getRealPath(user.getAvatar());
			String avatarFull = fileUploadUtils.getRealPath(user.getAvatarFull());
			Thumbnails.Builder<File> builder = Thumbnails.of(avatarFull);
			builder.sourceRegion(x,y,width,height).size(128,128).outputFormat("png").toFile(avatar);
			return user.getAvatar();
		} catch (Exception e) {
			e.printStackTrace();
			throw new BusinessException("头像裁剪出错");
		}

	}
	@Autowired
	private SpringValidatorAdapter validatorAdapter;

	@Override
	public void updateProfilePassword(Long id, @Valid ProfilePasswordDTO dto) {
		BindException bindException = new BindException(dto, "profilePasswordDTO");
		validatorAdapter.validate(dto,bindException);
		if(bindException.hasErrors()){
			throw BusinessException.with(bindException);
		}
		User user = userRepository.findOne(id);
		if(!passwordEncoder.matches(dto.getOldPassword(),user.getPassword())){
			throw new BusinessException("oldPassword","旧密码不正确");
		}
		if(!dto.getPassword().equals(dto.getPasswordConfirmation())){
			throw new BusinessException("passwordConfirmation","两次输入密码不一致");
		}
		user.setPassword(passwordEncoder.encode(dto.getPassword()));
		user.setPasswordLastModify(System.currentTimeMillis());
		userRepository.save(user);
	}


	@Override
	public void updateProfileEmail(Long id, ProfileEmailDTO dto) {
		BindException bindException = new BindException(dto, "profileEmailDTO");
		validatorAdapter.validate(dto,bindException);
		if(bindException.hasErrors()){
			throw BusinessException.with(bindException);
		}
		User user = userRepository.findOne(id);
		user.setEmail(dto.getEmail());
		user.getData().setEmailVerified(false);
		userRepository.save(user);

		//TODO 发送验证邮件
        sendEmailToken(id);
	}

    @Override
    public String sendEmailToken(Long id) {
	    //tooFast 发送太频繁返回
        return "success";

    }

	@Override
	public void updateProfileNotification(Long uid, NotificationType[] emailNotifications, NotificationType[] siteNotifications) {
		User user = userRepository.findOne(uid);
		Set<NotificationType> emailNotificationSets = Arrays.stream(emailNotifications).collect(Collectors.toSet());
		Set<NotificationType> siteNotificationSets = Arrays.stream(siteNotifications).collect(Collectors.toSet());
		user.setEmailNotifications(emailNotificationSets);
		user.setSiteNotifications(siteNotificationSets);
		userRepository.save(user);
	}
}
